// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <hw/sdmmc.h>
#include <hwreg/bitfields.h>

namespace sdmmc {

class MsdcCfg : public hwreg::RegisterBase<MsdcCfg, uint32_t> {
 public:
  static constexpr uint32_t kCardCkModeDiv = 0;
  static constexpr uint32_t kCardCkModeNoDiv = 1;
  static constexpr uint32_t kCardCkModeDdr = 2;
  static constexpr uint32_t kCardCkModeHs400 = 3;

  static auto Get() { return hwreg::RegisterAddr<MsdcCfg>(0x00); }

  DEF_FIELD(21, 20, card_ck_mode);
  DEF_BIT(18, hs400_ck_mode);
  DEF_FIELD(15, 8, card_ck_div);
  DEF_BIT(7, card_ck_stable);
  DEF_BIT(3, pio_mode);
  DEF_BIT(2, reset);
  DEF_BIT(1, ck_pwr_down);
};

class MsdcIoCon : public hwreg::RegisterBase<MsdcIoCon, uint32_t> {
 public:
  static constexpr uint32_t kSampleRisingEdge = 0;
  static constexpr uint32_t kSampleFallingEdge = 1;

  static auto Get() { return hwreg::RegisterAddr<MsdcIoCon>(0x04); }

  DEF_BIT(2, data_sample);
  DEF_BIT(1, cmd_sample);
};

class MsdcInt : public hwreg::RegisterBase<MsdcInt, uint32_t> {
 public:
  static constexpr uint32_t kAllInterruptBits = 0xffffffff;

  static auto Get() { return hwreg::RegisterAddr<MsdcInt>(0x0c); }

  bool CmdInterrupt() { return cmd_ready() || cmd_timeout() || cmd_crc_err(); }

  bool DataInterrupt() {
    return transfer_complete() || data_timeout() || data_crc_err() || bd_checksum_err() ||
           gpd_checksum_err();
  }

  DEF_BIT(18, gpd_checksum_err);
  DEF_BIT(17, bd_checksum_err);
  DEF_BIT(15, data_crc_err);
  DEF_BIT(14, data_timeout);
  DEF_BIT(12, transfer_complete);
  DEF_BIT(10, cmd_crc_err);
  DEF_BIT(9, cmd_timeout);
  DEF_BIT(8, cmd_ready);
  DEF_BIT(7, sdio_irq);
};

class MsdcIntEn : public hwreg::RegisterBase<MsdcIntEn, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<MsdcIntEn>(0x10); }

  DEF_BIT(18, gpd_checksum_err_enable);
  DEF_BIT(17, bd_checksum_err_enable);
  DEF_BIT(15, data_crc_err_enable);
  DEF_BIT(14, data_timeout_enable);
  DEF_BIT(12, transfer_complete_enable);
  DEF_BIT(10, cmd_crc_err_enable);
  DEF_BIT(9, cmd_timeout_enable);
  DEF_BIT(8, cmd_ready_enable);
  DEF_BIT(7, sdio_irq_enable);
};

class MsdcFifoCs : public hwreg::RegisterBase<MsdcFifoCs, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<MsdcFifoCs>(0x14); }

  DEF_BIT(31, fifo_clear);
  DEF_FIELD(23, 16, tx_fifo_count);
  DEF_FIELD(7, 0, rx_fifo_count);
};

class MsdcTxData : public hwreg::RegisterBase<MsdcTxData, uint8_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<MsdcTxData>(0x18); }

  DEF_FIELD(7, 0, data);
};

class MsdcRxData : public hwreg::RegisterBase<MsdcRxData, uint8_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<MsdcRxData>(0x1c); }

  DEF_FIELD(7, 0, data);
};

class SdcCfg : public hwreg::RegisterBase<SdcCfg, uint32_t> {
 public:
  static constexpr uint32_t kReadTimeoutMax = 0xff;
  static constexpr uint32_t kWriteTimeoutMax = 0x1fff;

  static constexpr uint32_t kBusWidth1 = 0;
  static constexpr uint32_t kBusWidth4 = 1;
  static constexpr uint32_t kBusWidth8 = 2;

  static auto Get() { return hwreg::RegisterAddr<SdcCfg>(0x30); }

  DEF_FIELD(31, 24, read_timeout);
  DEF_BIT(20, sdio_interrupt_enable);
  DEF_BIT(19, sdio_enable);
  DEF_FIELD(17, 16, bus_width);
};

class SdcCmd : public hwreg::RegisterBase<SdcCmd, uint32_t> {
 public:
  static constexpr uint32_t kAutoCmd12 = 1;

  static constexpr uint32_t kBlockTypeSingle = 1;
  static constexpr uint32_t kBlockTypeMulti = 2;

  static constexpr uint32_t kRespTypeR1 = 1;
  static constexpr uint32_t kRespTypeR2 = 2;
  static constexpr uint32_t kRespTypeR3 = 3;
  static constexpr uint32_t kRespTypeR4 = 4;
  static constexpr uint32_t kRespTypeR1b = 7;

  static auto Get() { return hwreg::RegisterAddr<SdcCmd>(0x34); }

  static SdcCmd FromRequest(const sdmmc_req_t* req, bool is_sdio) {
    SdcCmd cmd = Get().FromValue(0);

    cmd.set_cmd(req->cmd_idx);
    uint32_t resp_type =
        req->cmd_flags & (SDMMC_RESP_R1 | SDMMC_RESP_R2 | SDMMC_RESP_R3 | SDMMC_RESP_R1b);
    if (resp_type == SDMMC_RESP_R1) {
      cmd.set_resp_type(kRespTypeR1);
    } else if (resp_type == SDMMC_RESP_R2) {
      cmd.set_resp_type(kRespTypeR2);
    } else if (resp_type == SDMMC_RESP_R3) {
      cmd.set_resp_type(kRespTypeR3);
    } else if (resp_type == SDMMC_RESP_R1b) {
      cmd.set_resp_type(kRespTypeR1b);
    }

    cmd.set_block_size(req->blocksize);

    if (req->cmd_flags & SDMMC_RESP_DATA_PRESENT) {
      if (req->blockcount > 1) {
        if (!is_sdio) {
          cmd.set_auto_cmd(kAutoCmd12);
        }

        cmd.set_block_type(kBlockTypeMulti);
      } else {
        cmd.set_block_type(kBlockTypeSingle);
      }

      if (!(req->cmd_flags & SDMMC_CMD_READ)) {
        cmd.set_write(1);
      }
    }

    if ((req->cmd_flags & SDMMC_CMD_TYPE_ABORT) && !is_sdio) {
      cmd.set_stop(1);
    }

    return cmd;
  }

  DEF_FIELD(29, 28, auto_cmd);
  DEF_FIELD(27, 16, block_size);
  DEF_BIT(14, stop);
  DEF_BIT(13, write);
  DEF_FIELD(12, 11, block_type);
  DEF_FIELD(9, 7, resp_type);
  DEF_FIELD(5, 0, cmd);
};

class SdcArg : public hwreg::RegisterBase<SdcArg, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<SdcArg>(0x38); }
};

class SdcStatus : public hwreg::RegisterBase<SdcStatus, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<SdcStatus>(0x3c); }

  uint32_t busy() { return cmd_busy() || sdc_busy(); }

  DEF_BIT(1, cmd_busy);
  DEF_BIT(0, sdc_busy);
};

class SdcResponse : public hwreg::RegisterBase<SdcResponse, uint32_t> {
 public:
  static auto Get(int index) { return hwreg::RegisterAddr<SdcResponse>(0x40 + (index << 2)); }

  DEF_FIELD(31, 0, response);
};

class SdcBlockNum : public hwreg::RegisterBase<SdcBlockNum, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<SdcStatus>(0x50); }
};

class DmaStartAddrHigh4Bits : public hwreg::RegisterBase<DmaStartAddrHigh4Bits, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<DmaStartAddrHigh4Bits>(0x8c); }

  DEF_FIELD(3, 0, address);

  DmaStartAddrHigh4Bits& set(uint64_t addr) {
    set_address((addr & kAddressMask) >> 32);
    return *this;
  }

 private:
  static constexpr uint64_t kAddressMask = 0xf00000000;
};

class DmaStartAddr : public hwreg::RegisterBase<DmaStartAddr, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<DmaStartAddr>(0x90); }

  DEF_FIELD(31, 0, address);

  DmaStartAddr& set(uint64_t addr) {
    set_address(addr & kAddressMask);
    return *this;
  }

 private:
  static constexpr uint64_t kAddressMask = 0xffffffff;
};

class DmaCtrl : public hwreg::RegisterBase<DmaCtrl, uint32_t> {
 public:
  static constexpr uint32_t kDmaModeBasic = 0;
  static constexpr uint32_t kDmaModeDescriptor = 1;

  static auto Get() { return hwreg::RegisterAddr<DmaCtrl>(0x98); }

  DEF_BIT(10, last_buffer);
  DEF_BIT(8, dma_mode);
  DEF_BIT(1, dma_stop);
  DEF_BIT(0, dma_start);
};

class DmaCfg : public hwreg::RegisterBase<DmaCfg, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<DmaCfg>(0x9c); }

  DEF_BIT(1, checksum_enable);
  DEF_BIT(0, dma_active);
};

class DmaLength : public hwreg::RegisterBase<DmaLength, uint32_t> {
 public:
  static auto Get() { return hwreg::RegisterAddr<DmaLength>(0xa8); }
};

class PadTune0 : public hwreg::RegisterBase<PadTune0, uint32_t> {
 public:
  static constexpr int kDelayMax = 0x1f;

  static auto Get() { return hwreg::RegisterAddr<PadTune0>(0xf0); }

  DEF_BIT(21, cmd_delay_sel);
  DEF_FIELD(20, 16, cmd_delay);
  DEF_BIT(13, data_delay_sel);
  DEF_FIELD(12, 8, data_delay);
};

class GpDmaDescriptorInfo : public hwreg::RegisterBase<GpDmaDescriptorInfo, uint32_t> {
 public:
  DEF_FIELD(31, 28, bdma_desc_addr_high_4_bits);
  DEF_FIELD(27, 24, next_addr_high_4_bits);
  DEF_FIELD(15, 8, checksum);
  DEF_BIT(1, bdp);
  DEF_BIT(0, hwo);

  GpDmaDescriptorInfo& set_bdma_desc_addr(uint64_t addr) {
    set_bdma_desc_addr_high_4_bits((addr & kAddressMask) >> 32);
    return *this;
  }

  GpDmaDescriptorInfo& set_next_addr(uint64_t addr) {
    set_next_addr_high_4_bits((addr & kAddressMask) >> 32);
    return *this;
  }

 private:
  static constexpr uint64_t kAddressMask = 0xf00000000;
};

class BDmaDescriptorInfo : public hwreg::RegisterBase<BDmaDescriptorInfo, uint32_t> {
 public:
  DEF_FIELD(31, 28, buffer_addr_high_4_bits);
  DEF_FIELD(27, 24, next_addr_high_4_bits);
  DEF_FIELD(15, 8, checksum);
  DEF_BIT(0, last);

  BDmaDescriptorInfo& set_buffer_addr(uint64_t addr) {
    set_buffer_addr_high_4_bits((addr & kAddressMask) >> 32);
    return *this;
  }

  BDmaDescriptorInfo& set_next_addr(uint64_t addr) {
    set_next_addr_high_4_bits((addr & kAddressMask) >> 32);
    return *this;
  }

 private:
  static constexpr uint64_t kAddressMask = 0xf00000000;
};

}  // namespace sdmmc
